/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.persistence.hibernate.mapping.type;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.hibernate.HibernateException;
import org.hibernate.type.NullableType;

/**
 * This class allows strings to be stored in an escaped form, so that they will
 * never be automatically converted to NULL values by the database, should they
 * be empty. Note that this class will not allow you to use NULL value strings
 * when they are not allowed by Hibernate (such as in Maps). Version for
 * Hibernate 3 that does not add quotes to non-empty strings but escapes by a
 * keyword. This seems more economic in cases where empty strings are rare.
 * 
 * @author Erik Visser, Chess-iT B.V.
 * @author Mika Goeckel, cyber:con gmbh
 */
public abstract class AbstractHibernateEscapedCharArray
    extends AbstractImmutableUserType
{

  /**
   * Markup value for empty string.
   */
  private static final String MARK_EMPTY = "_";

  /**
   * Markup value for empty string.
   */
  private static final String MARK_NULL = null;

  /**
   * the hibernate type associated with this
   * {@link org.hibernate.usertype.UserType}.
   */
  private transient final NullableType type;



  /**
   * constructor.
   * 
   * @param type
   *          the hibernate type associated with this
   *          {@link org.hibernate.usertype.UserType}
   */
  protected AbstractHibernateEscapedCharArray(NullableType type)
  {
    this.type = type;
  }



  /**
   * Retrieve an instance of the mapped class from a JDBC resultset.
   * Implementors should handle possibility of null values.
   * 
   * @param rs
   *          a JDBC result set
   * @param names
   *          the column names
   * @param owner
   *          the containing entity
   * @return Object
   * @throws HibernateException
   *           if a hibernate error occurred
   * @throws SQLException
   *           if a SQL error occurred
   */
  public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
      throws HibernateException, SQLException
  {
    String dbValue = (String) type.nullSafeGet(rs, names[0]);
    if (dbValue != null) {
      return unescape(dbValue);
    }
    else {
      return null;
    }
  }



  /**
   * Write an instance of the mapped class to a prepared statement. Implementors
   * should handle possibility of null values. A multi-column type should be
   * written to parameters starting from <tt>index</tt>.
   * 
   * @param st
   *          a JDBC prepared statement
   * @param value
   *          the object to write
   * @param index
   *          statement parameter index
   * @throws HibernateException
   *           if a hibernate error occurred
   * @throws SQLException
   *           if a SQL error occurred
   */
  public void nullSafeSet(PreparedStatement st, Object value, int index)
      throws HibernateException, SQLException
  {

    if (value != null) {
      String v = escape((String) value);
      type.nullSafeSet(st, v, index);
    }
    else {
      type.nullSafeSet(st, value, index);
    }
  }



  /**
   * The class returned by <tt>nullSafeGet()</tt>.
   * 
   * @return Class
   */
  public Class<?> returnedClass()
  {
    return String.class;
  }



  /**
   * Return the SQL type codes for the columns mapped by this type. The codes
   * are defined on <tt>java.sql.Types</tt>.
   * 
   * @see java.sql.Types
   * @return int[] the typecodes
   */
  public int[] sqlTypes()
  {
    return new int[] { type.sqlType() };
  }



  /**
   * Escape a {@link String} to be entered in the database.
   * 
   * @param string
   *          the {@link String} to be escaped
   * @return the escaped {@link String}
   */
  private String escape(String string)
  {
    if ((string == null)) {
      return MARK_NULL;
    }
    if (string.length() == 0) {
      return MARK_EMPTY;
    }
    if (string.startsWith(MARK_EMPTY)) {
      string += MARK_EMPTY;
    }
    return string;
  }



  /**
   * Unescape a {@link String} from the database.
   * 
   * @param string
   *          the {@link String} to be unescaped
   * @return the unescaped {@link String}
   */
  private Object unescape(String string)
  {
    if (string == null || string.length() == 0 || string.equals(MARK_NULL)) {
      return null;
    }
    if (string.startsWith(MARK_EMPTY)) {
      string = string.substring(MARK_EMPTY.length());
    }
    return string;
  }

}
